import json
import re

import cv2
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import seaborn as sn
from sklearn.metrics import confusion_matrix, classification_report

"""
数据分析工具类
"""


def img_show(vector):
    """
    展示图片
    :param vector: 图片矩阵
    :return:
    """
    cv2.imshow(" ", cv2.resize(vector, (2400, 1480)))
    cv2.waitKey(0)


def plot_model_history(model_history, out_path=""):
    """
    创建两个图表，一个是准确率，一个是损失率，以显示这两个指标在训练过程中的演变。
    :param model_history: 历史训练记录
    :param out_path: 输出路径
    :return: 无
    """
    fig, axs = plt.subplots(1, 2, figsize=(15, 5))
    # 训练集与验证集准确率accuracy
    axs[0].plot(range(1, len(model_history.history['accuracy']) + 1), model_history.history['accuracy'])
    axs[0].plot(range(1, len(model_history.history['val_accuracy']) + 1), model_history.history['val_accuracy'])
    axs[0].set_title('Model Accuracy')
    axs[0].set_ylabel('Accuracy')
    axs[0].set_xlabel('Epoch')
    axs[0].set_xticks(np.arange(1, len(model_history.history['accuracy']) + 1))
    axs[0].legend(['train', 'valid'], loc='best')
    # 训练集与验证集损失loss
    axs[1].plot(range(1, len(model_history.history['loss']) + 1), model_history.history['loss'])
    axs[1].plot(range(1, len(model_history.history['val_loss']) + 1), model_history.history['val_loss'])
    axs[1].set_title('Model Loss')
    axs[1].set_ylabel('Loss')
    axs[1].set_xlabel('Epoch')
    axs[1].set_xticks(np.arange(1, len(model_history.history['loss']) + 1))
    axs[1].legend(['train', 'valid'], loc='best')
    # 将图形保存在一个名为 "model_name_history.png"的文件中，以便日后使用
    if out_path:
        plt.savefig(out_path + "/history.png")
    # plt.show()


def plot_confusion_matrix(y_true: [], y_pred: [], classes: [], out_path=""):
    """
    创建一个混淆矩阵，直观地表示错误分类的图像

    :param y_true: 真实值
    :param y_pred: 预测值
    :param classes: 类别名称列表
    :param out_path: 输出路径
    :return: 混淆矩阵
    """
    cm = confusion_matrix(y_true, y_pred)
    df_cm = pd.DataFrame(cm, index=[i for i in classes], columns=[i for i in classes])
    size = max(int(len(classes) / 2), 40)
    plt.figure(figsize=(size, size))
    ax = sn.heatmap(df_cm, annot=False, square=True, fmt="d", linewidths=.0, cbar_kws={"shrink": 0.8},
                    cmap=plt.cm.Blues)
    if out_path:
        plt.savefig(out_path + "/confusion_matrix.png")  # 矩阵被保存在一个名为 "model_name_confusion_matrix.png "的文件中
        df_cm.to_csv(out_path + "/confusion_matrix.csv")
    return ax


def get_lower_performance_classes(report_path: str, threshold: float = 0.5, metrics: str = 'f1'):
    """
    获得性能低的那些类别

    :param report_path: classification_report.txt的路径
    :param threshold: 性能指标的阈值
    :param metrics: 性能指标，只能是:precision;recall;f1
    :return: 性能低的那些类别列表
    """
    key_val = '/'.join(report_path.split('/')[:-1]) + '/key_val.json'
    metrics_dict = {'precision': 1, 'recall': 2, 'f1': 3}
    if metrics_dict.get(metrics) is None:
        print('输入的指标不正确！只能输入"precision","recall","f1"')
        return
    template = '{:^15}{:^15}{:^15}{:^15}{:^15}'
    print('{:-^75}'.format('总结果'))
    print(template.format('class_name', 'precision', 'recall', 'f1-score', 'support'), '\n')
    results = []
    with open(key_val, mode='r', encoding='utf-8') as f:
        keys = json.loads(f.read())
    low_performance = []
    with open(report_path, mode='r', encoding='utf-8') as f:
        for line in f.readlines():
            line = re.split('\\s+', line.strip())
            if len(line) == 5:
                line[0] = keys.get(line[0], line[0])
                for i in range(1, 4):
                    line[i] = float(line[i])
                line[4] = int(line[4])
                results.append(line)
    results = sorted(results, key=lambda x: x[metrics_dict[metrics]], reverse=True)
    for line in results:
        print(template.format(line[0], line[1], line[2], line[3], line[4]))
    print('{:-^75}'.format('{}小于{}的结果').format(metrics, threshold))
    print(template.format('class_name', 'precision', 'recall', 'f1-score', 'support'), '\n')
    for line in results[::-1]:
        if float(line[metrics_dict[metrics]]) < threshold:
            low_performance.append(line[0])
            print(template.format(line[0], line[1], line[2], line[3], line[4]))
    return low_performance


def loose_classification_report(y_true, y_pred, target_names=None, threshold=0.1):
    """
    宽松的classification_report，当概率大于threshold时，就认为这些标签都是输出的标签，如果y_true在这些标签之中，则认为此样本预测准确
    如：y_true = [1];y_pred[[0.5, 0.1, 0.001]],则该样本预测为[0, 1]，预测正确

    :param y_true: 一维数组，真值
    :param y_pred: 二维数组，预测值
    :param target_names: 类名
    :param threshold: 阈值
    :return: 报告
    """
    y_pred_temp = []
    for index, y in enumerate(y_pred):
        temp = set()
        for i in range(len(y)):
            if y[i] >= threshold:
                temp.add(i)
        if y_true[index] in temp:
            y_pred_temp.append(y_true[index])
        else:
            y_pred_temp.append(np.argmax(y))
    return classification_report(y_true, y_pred_temp, target_names=target_names)


def analyse_confusion_matrix(file_path: str, most_n_classes: int):
    """
    分析混淆矩阵，输出每个类别被预测的最多样本数的前most_n_classes个类

    :param file_path: 混淆矩阵路径
    :param most_n_classes: 每个类别被预测最多样本数的前most_n_classes个类
    :return:
    """
    with open('/'.join(file_path.split('/')[:-1]) + '/key_val.json', mode='r', encoding='utf-8') as f:
        keys = json.loads(f.read())
    confusion_matrix = pd.read_csv(file_path)
    class_names = confusion_matrix.columns.values.tolist()[1:]
    template = '{:<15}{:<10}{:<}'
    print(template.format('类名', 'recall', '被预测得最多得类名(按样本数递减)'))
    for index, row in confusion_matrix.iterrows():
        class_name = keys['{}'.format(row[0])]
        temp = row[1:]
        self_num = temp[index]
        summary = sum(temp)
        most_classes = []
        for _ in range(most_n_classes):
            max_index = np.argmax(temp)
            most_classes.append('{}:{}'.format(keys['{}'.format(class_names[max_index])], temp[max_index]))
            temp[max_index] = 0
        print(template.format(class_name, round(self_num / summary, 3), '{}'.format(most_classes)))


if __name__ == '__main__':
    # 分析classification_report
    get_lower_performance_classes(
        'output_files/cnn/classification_report.txt',
        threshold=0.2,
        metrics='f1')

    # 分析confusion_matrix
    analyse_confusion_matrix('output_files/cnn/confusion_matrix.csv', most_n_classes=3)
